package com.katze.boot.plugins.redis;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import io.lettuce.core.ClientOptions;
import io.lettuce.core.TimeoutOptions;
import io.lettuce.core.protocol.ProtocolVersion;
import lombok.SneakyThrows;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 说  明：
 * 作  者：zf.zeng
 * 日  期：2022-11-10 (星期四)
 **/
public class RedisRegister implements EnvironmentAware, ImportBeanDefinitionRegistrar {

    private String host;
    private int port;
    private String password;
    private Duration timeout = Duration.ofMillis(3000);
    private String database = "0";
    private String serializer;
    private String db;
    private String factory = "Lettuce";
    private final GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
    public static final Map<String, Boolean> REDIS_TEMPLATE_POOL = new HashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        RedisProperties properties = Binder.get(environment).bind("spring.data.redis", RedisProperties.class).get();
        this.host = properties.getHost();
        this.port = properties.getPort();
        this.password = properties.getPassword();
        if(properties.getTimeout() != null) this.timeout = properties.getTimeout();
        this.database = String.valueOf(properties.getDatabase());
        this.serializer = environment.getProperty("spring.data.redis.serializer", "com.katze.boot.plugins.redis.serializer.JacksonRedisSerializer");
        this.db = environment.getProperty("spring.data.redis.databases.db", this.database);
        if (Boolean.TRUE.equals(properties.getJedis().getPool().getEnabled())) {
            this.factory = "Jedis";
            this.initPool(properties.getJedis().getPool());
        } else if (Boolean.TRUE.equals(properties.getLettuce().getPool().getEnabled())) {
            this.factory = "Lettuce";
            this.initPool(properties.getLettuce().getPool());
        }
    }

    /**
     * springboot3 lettuce-core版本6.x以上无法切换database = 1+
     * @RESP2：如果你使用的是Redis 6.x或更低版本，或者你的应用程序依赖于某些只在RESP2中支持的功能，建议继续使用 ProtocolVersion.RESP2。
     * @RESP3：如果你使用的是Redis 7.0或更高版本，并且希望利用RESP3提供的新功能（如块状回复、属性字典等），则可以考虑使用 ProtocolVersion.RESP3。RESP3提供了更丰富的响应类型和更好的性能，特别是在处理复杂数据结构时。
     */
    @SneakyThrows
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
        String[] dbs = db.split(StringPool.COMMA);
        RedisSerializer<?> redisSerializer = (RedisSerializer<?>) Class.forName(serializer).getDeclaredConstructor().newInstance();
        for (String index : dbs) {
            RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
            configuration.setHostName(host);
            configuration.setPort(port);
            configuration.setPassword(RedisPassword.of(password));
            configuration.setDatabase(Integer.parseInt(index));

            RedisConnectionFactory connectionFactory;
            if (factory.equals("Lettuce")) {
                ClientOptions clientOptions = ClientOptions.builder().timeoutOptions(TimeoutOptions.enabled()).protocolVersion(ProtocolVersion.RESP2).build();
                LettuceClientConfiguration clientConfiguration = LettucePoolingClientConfiguration.builder().commandTimeout(timeout).poolConfig(poolConfig).clientOptions(clientOptions).build();
                connectionFactory = new LettuceConnectionFactory(configuration, clientConfiguration);

                ((LettuceConnectionFactory) connectionFactory).afterPropertiesSet();
            } else {
                JedisClientConfiguration clientConfiguration = JedisClientConfiguration.builder().connectTimeout(timeout).usePooling().poolConfig(poolConfig).build();
                connectionFactory = new JedisConnectionFactory(configuration, clientConfiguration);
                ((JedisConnectionFactory) connectionFactory).afterPropertiesSet();
            }
            GenericBeanDefinition redisTemplate = new  GenericBeanDefinition();
            redisTemplate.setBeanClass(RedisTemplate.class);
            redisTemplate.getPropertyValues().add("connectionFactory",connectionFactory);
            redisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE);
            redisTemplate.getPropertyValues().add("keySerializer",new StringRedisSerializer());
            redisTemplate.getPropertyValues().add("hashKeySerializer",new  StringRedisSerializer());
            redisTemplate.getPropertyValues().add("valueSerializer", redisSerializer);
            redisTemplate.getPropertyValues().add("hashValueSerializer", redisSerializer);

            redisTemplate.setPrimary(index.equals(this.database));

            String name = RedisTemplate.class.getSimpleName() +  index;
            beanDefinitionRegistry.registerBeanDefinition(name, redisTemplate);
            REDIS_TEMPLATE_POOL.put(index, redisTemplate.isPrimary());
        }
    }

    private void initPool(RedisProperties.Pool pool){
        poolConfig.setMinIdle(pool.getMinIdle()); //最小空闲连接数
        poolConfig.setMaxIdle(pool.getMaxIdle()); // 最大空闲连接数, 默认8个
        poolConfig.setMaxTotal(pool.getMaxActive()); // 最大连接数, 默认值8个
        poolConfig.setMaxWait(pool.getMaxWait()); // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间,  默认-1
        if(pool.getTimeBetweenEvictionRuns() != null) {//每隔多久检测一次
            poolConfig.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
        }
        poolConfig.setTestOnBorrow(true);//从连接池获取连接时，检查连接是否可用,业务量很大时候建议设置为false，减少一次ping的开销
        poolConfig.setTestOnReturn(true); //归还连接时，会进行检查，检查不通过，销毁,业务量很大时候建议设置为false，减少一次ping的开销。
        poolConfig.setTestWhileIdle(true);//开启空闲连接检测
        poolConfig.setNumTestsPerEvictionRun(8);//每次最多检测8个连接
    }

}
